---@class Utils
Utils = {
    MapMarkupIDs = {
        [1] = false,
    },
}

local version = {
    MAJOR = 2,
    MINOR = 0,
    PATCH = 9,
    Special = 'public'
}

do
--- Miscellaneous functions.
    --- Converts a TACAN Channel/Mode couple into a frequency in Hz.
    ---@param TACANChannel number  The TACAN channel, i.e. the 10 in "10X".
    ---@param TACANMode string  The TACAN mode, i.e. the "X" in "10X".
    ---@return number? Frequency in Hz or #nil if parameters are invalid.
    function Utils.TACANToFrequency(TACANChannel, TACANMode)

        if type(TACANChannel) ~= "number" then
            return nil -- error in arguments
        end
        if TACANMode ~= "X" and TACANMode ~= "Y" then
            return nil -- error in arguments
        end

        -- This code is largely based on ED's code, in DCS World\Scripts\World\Radio\BeaconTypes.lua, line 137.
        -- I have no idea what it does but it seems to work
        local A = 1151 -- 'X', channel >= 64
        local B = 64   -- channel >= 64

        if TACANChannel < 64 then
        B = 1
        end

        if TACANMode == 'Y' then
        A = 1025
        if TACANChannel < 64 then
            A = 1088
        end
        else -- 'X'
        if TACANChannel < 64 then
            A = 962
        end
        end

        return (A + TACANChannel - B) * 1000000
    end

    ---Get wind bearing and velocity.
    ---@param point vec2|vec3 point with altitude.
    ---@return number radians and number.
    ---@return number wind speed number.
    function Utils.getWindBearingAndVel(point)
        local makeVec3 = Utils.makeVec3

        local tPoint = makeVec3(point)
        local gLevel = land.getHeight({x = tPoint.x, y = tPoint.z})
        if tPoint.y <= gLevel then
            tPoint.y = gLevel + 10
        end
        local t = atmosphere.getWind(tPoint)
        local bearing = math.atan2(t.z, t.x)
        local vel = math.sqrt(t.x^2 + t.z^2)

        return bearing, vel
    end

    --- Make a given string work as script.
    ---@param s string to do.
    ---@return boolean success.
    ---@return function|string?
    function Utils.doString(s)
        local f, err = loadstring(s)
        if f then
            return true, f()
        else
            return false, err
        end
    end
--- Table functions.
    --- Get table size.
    ---@param table table
    ---@return number? Size of this table.
    function Utils.getTableSize(table)
        if not table then return end

        local tableSize = 0
        for __ in pairs(table) do
            tableSize = tableSize + 1
        end

        return tableSize
    end

    --- Show table. (simple table)
    ---@param table table to show. not a complex table though.
    ---@param level? number level of this sub table.
    ---@return string Result formated .
    function Utils.tableShow(table, level)
        local getTableSize = Utils.getTableSize
        local tableShow = Utils.tableShow

        if type(table) ~= 'table' then return tostring(table) end
        if getTableSize(table) <= 0 then return '{}' end

        level = level or 1
        local pushBack = '  '

        local result = '{\n'
        for index,value in pairs(table) do
            for i=1,level,1 do
                result = result..pushBack
            end
            result = result..'[\"'..index..'\"] = '

            if type(value) == 'table' then
                result = result..tableShow(value,level+1)..',\n'
            end

            if type(value) == 'string' then
                result = result..'\"'..value..'\",\n'
            end

            if type(value) == 'number' then
                result = result..value..',\n'
            end

            if type(value) == 'function' then
                result = result..'[function] '..value..',\n'
            end

            if type(value) == 'boolean' then
                result = result..tostring(value)..',\n'
            end
        end

        if level > 1 then
            for i=2,level,1 do
                result = result..pushBack
            end
        end

        return result..'}'
    end

    --- Deep copy.
    ---@param object table
    ---@return table
    function Utils.deepCopy(object)
		local lookup_table = {}
		local function _copy(object)
			if type(object) ~= "table" then
				return object
			elseif lookup_table[object] then
				return lookup_table[object]
			end
			local new_table = {}
			lookup_table[object] = new_table
			for index, value in pairs(object) do
				new_table[_copy(index)] = _copy(value)
			end
			return setmetatable(new_table, getmetatable(object))
		end
		return _copy(object)
	end

--- Debug functions.
    --- Debug.
    ---@param text string text to desplay.
    ---@param displayTime? number in second for how long the message will display.
    ---@param clearview? boolean if will clear other messages.
    function Utils.Debug(text,displayTime,clearview,title)
        local tableShow = Utils.tableShow

        displayTime = displayTime or 10

        local title = title or '[Debug] '

        if type(text)=='table' then
            text = tableShow(text)
        end
        if type(text)~='string' then text = tostring(text) end

        text = title..text
        trigger.action.outText(text,displayTime)
        env.info(text)
    end

--- Vector functions.
    --- Vector add.
    ---@param vec1 vec3 first vector.
    ---@param vec2 vec3 second vector.
    ---@return vec3 result.
    function Utils.vecAdd(vec1, vec2)
		return {x = vec1.x + vec2.x, y = vec1.y + vec2.y, z = vec1.z + vec2.z}
	end

    --- Vector substraction.
    ---@param vec1 vec3 first vector.
    ---@param vec2 vec3 second vector.
    ---@return vec3 result.
    function Utils.vecSub(vec1, vec2)
		return {x = vec1.x - vec2.x, y = vec1.y - vec2.y, z = vec1.z - vec2.z}
	end

    --- Translate a vector from the start point by the given degree and distance.
    ---@param vec3 vec3 start point.
    ---@param degree number from 0 to 360 or more.
    ---@param distance number in meters.
    ---@return vec3
    function Utils.vecTranslate(vec3,degree,distance)
        local point = {x = vec3.x,y = vec3.z or vec3.y,}

        if distance == 0 then return point end

        local radian = math.rad(degree)

        point.x = point.x + distance * math.cos(radian)
        point.y = point.y + distance * math.sin(radian)
        return point
    end

    --- Converts a vec3 to a vec2.
	---@param vec vec2|vec3 the 3D vector
	---@return vec2?
	function Utils.makeVec2(vec)
        if not vec then return end

		if vec.z then
			return {x = vec.x, y = vec.z}
		else
			return {x = vec.x, y = vec.y}	-- it was actually already vec2.
		end
	end

    --- Converts any vec to a vec3.
    ---@param vec vec2|vec3 the 3D vector
    ---@param AGL boolean|nil if to use above ground level in meters.
	---@return vec3
    function Utils.makeVec3(vec,AGL)
        local deepCopy = Utils.deepCopy

		local result = deepCopy(vec)
        if not vec.z then
            result.z = vec.y
            result.y = 0
            if AGL then
                result.y = land.getHeight(vec)
            end
        end

        return result
	end

    --- Vector magnitude
	---@param vec vec3 vector
	---@return number magnitude of vector vec
    function Utils.mag(vec)
		return (vec.x^2 + vec.y^2 + vec.z^2)^0.5
	end

--- DCS.Point & DCS.Zone functions.
    --- Returns distance in meters between two points in 2D space.
	---@param point1 vec2|vec3 first point.
	---@param point2 vec2|vec3 second point.
	---@return number distance between given points in 2D space.
	function Utils.get2DDist(point1, point2)
        local makeVec3 = Utils.makeVec3
        local mag = Utils.mag

        if not point1 then
            env.warning("Utils.get2DDist 1st input value is nil")
        end
        if not point2 then
            env.warning("Utils.get2DDist 2nd input value is nil")
        end
		point1 = makeVec3(point1)
		point2 = makeVec3(point2)
		return mag({x = point1.x - point2.x, y = 0, z = point1.z - point2.z})
	end

	--- Returns distance in meters between two points in 3D space.
	---@param point1 vec3 first point
	---@param point2 vec3 second point
	---@return number distancen between given points in 3D space.
	function Utils.get3DDist(point1, point2)
        local makeVec3 = Utils.makeVec3
        local mag = Utils.mag

        if not point1 then
            env.warning("Utils.get3DDist  1st input value is nil")
        end
        if not point2 then
            env.warning("Utils.get3DDist  2nd input value is nil")
        end

        point1 = makeVec3(point1)
		point2 = makeVec3(point2)
		return mag({x = point1.x - point2.x, y = point1.y - point2.y, z = point1.z - point2.z})
	end

	---Gets the correction needed for true north.
    ---@param gPoint vec2|vec3
    ---@return number north-correct in radians.
    function Utils.getNorthCorrection(gPoint)
        local deepCopy = Utils.deepCopy

		local point = deepCopy(gPoint)
		if not point.z then --vec2; convert to vec3
			point.z = point.y
			point.y = 0
		end

		local lat, lon = coord.LOtoLL(point)
		local north_posit = coord.LLtoLO(lat + 1, lon)
		return math.atan2(north_posit.z - point.z, north_posit.x - point.x)
	end

    --- Returns heading-error corrected direction.
	-- True-north corrected direction from point along vector vec.
	---@param vec vec3 
	---@param point? vec3
	---@return number heading-error corrected direction from point in radians.
	function Utils.getDir(vec, point)
        local getNorthCorrection = Utils.getNorthCorrection

		local dir = math.atan2(vec.z, vec.x)
		if point then
			dir = dir + getNorthCorrection(point)
		end
		if dir < 0 then
			dir = dir + 2 * math.pi	-- put dir in range of 0 to 2*pi
		end
		return dir
	end

    ---Get direction from vec_1 to vec_2.
    ---@param vec3_1 vec3
    ---@param vec3_2 vec3
    ---@return number radians of direction. Use math.deg() to convert into degrees.
    function Utils.getDirection(vec3_1,vec3_2)
        local makeVec3 = Utils.makeVec3
        local getDir = Utils.getDir
        local vecSub = Utils.vecSub

        local p1 = makeVec3(vec3_1,true)
        local p2 = makeVec3(vec3_2,true)
        local dir = getDir(vecSub(p1, p2))

        return dir
    end

    ---Get average point in a table of points.
    ---@param points {[number]:vec3} of vec3 points.
    ---@return vec3 point.
    function Utils.getAvgPoint(points)
        local makeVec3 = Utils.makeVec3

        local avgX, avgY, avgZ, totNum = 0, 0, 0, 0
        for i = 1, #points do
            local nPoint = makeVec3(points[i])
            if nPoint.z then
                avgX = avgX + nPoint.x
                avgY = avgY + nPoint.y
                avgZ = avgZ + nPoint.z
                totNum = totNum + 1
            end
        end
        if totNum ~= 0 then
            return {x = avgX/totNum, y = avgY/totNum, z = avgZ/totNum}
        end
    end

    ---@class DCS.Zone
    ---@field point vec3 center of the zone
    ---@field radius number radius of the zone. Rough outter circle for a polygon
    ---@field verticies vec3[]? table of points for polygon

    ---Get a DCS.zone from [mission file] by zone name.
    ---Fix trigger.misc.getZone() can not return a correct vertic zone.
    ---@param zoneName string zoneName.
    ---@return DCS.Zone? Zone-table Formation: {point:vcc3, radius:number}|{point:vec3, verticies:table, radius:number}
    function Utils.getZone(zoneName)
        local makeVec3 = Utils.makeVec3

        if not zoneName then return end

        local triggerZones = env.mission.triggers.zones

        for i,zone in ipairs(triggerZones) do
            if zone.name ==  zoneName then
                --If zone is circle.
                if not zone.verticies then
                    return {point = {x=zone.x,y=0,z=zone.y}, radius = zone.radius}
                end

                if zone.verticies then
                    local result = {}
                    result.point = {
                        x=zone.x,
                        y=0,
                        z=zone.y,
                    }

                    result.radius = zone.radius

                    result.verticies = {}

                    for j,vert in ipairs(zone.verticies) do
                        result.verticies[j] = makeVec3(vert)
                    end

                    return result
                end
            end
        end

        return nil
    end

    ---Get random point in a given circle zone.
    ---@param vec vec2|vec3 center point of circle zone.
    ---@param radius number radius of circle zone.
    ---@param innerRadius? number smaller circle zone for not pick point from.
    ---@param maxA? number in degree. Maximum degree of a sector area in clockwise.
    ---@param minA? number in degree. Minimum degree of a sector area in clockwise.
    ---@return vec2 rndCoord --a founded random point.
    function Utils.getRandPointInCircle(vec, radius, innerRadius, maxA, minA)
        local makeVec3 = Utils.makeVec3

        radius = radius or 1000

        local point = makeVec3(vec)
        local theta = 2*math.pi*math.random()
        local minR = innerRadius or 0
        if maxA and not minA then
            theta = math.rad(math.random(0, maxA - math.random()))
        elseif maxA and minA then
            if minA < maxA then
                theta = math.rad(math.random(minA, maxA) - math.random())
            else
                theta = math.rad(math.random(maxA, minA) - math.random())
            end
        end
        local rad = math.random() + math.random()
        if rad > 1 then
            rad = 2 - rad
        end

        local radMult
        if minR and minR <= radius then
            --radMult = (radius - innerRadius)*rad + innerRadius
            radMult = radius * math.sqrt((minR^2 + (radius^2 - minR^2) * math.random()) / radius^2)
        else
            radMult = radius*rad
        end

        local rndCoord
        if radius > 0 then
            rndCoord = {x = math.cos(theta)*radMult + point.x, y = math.sin(theta)*radMult + point.z}
        else
            rndCoord = {x = point.x, y = point.z}
        end
        return rndCoord
    end

    ---Get random point in a given polygon zone.
    ---@param poly vec3[] {verticies[1]:vec3, verticies[2]:vec3, verticies[3]:vec3, ...}
    ---@return vec2
    function Utils.getRandomPointInPoly(poly)
        local getAvgPoint = Utils.getAvgPoint
        local get2DDist = Utils.get2DDist
        local getRandPointInCircle = Utils.getRandPointInCircle
        local pointInPolygon = Utils.pointInPolygon

        local avg = getAvgPoint(poly)
		local radius = 0
		local minR = math.huge
		local newCoord = {}
		for i = 1, #poly do
			if get2DDist(avg, poly[i]) > radius then
				radius = get2DDist(avg, poly[i])
			end
			if get2DDist(avg, poly[i]) < minR then
				minR = get2DDist(avg, poly[i])
			end
		end

		for j = 1, 100 do
			newCoord = getRandPointInCircle(avg, radius)
			if pointInPolygon(newCoord, poly) then
				break
			end
			if j == 100 then
				newCoord = getRandPointInCircle(avg, radius)
			end
		end
		return newCoord
	end

    ---Check if a given point inside polygon zone.
    ---@param point vec2|vec3
    ---@param poly vec3[] {verticies[1]:vec3, verticies[2]:vec3, verticies[3]:vec3, ...}
    ---@param maxalt number? max search altitude.
    function Utils.pointInPolygon(point, poly, maxalt)
        local makeVec3 = Utils.makeVec3
        local deepCopy = Utils.deepCopy

        point = makeVec3(point)
        local px = point.x
        local pz = point.z
        local cn = 0
        local newpoly = deepCopy(poly)

        if not maxalt or (point.y <= maxalt) then
            local polysize = #newpoly
            newpoly[#newpoly + 1] = newpoly[1]

            newpoly[1] = makeVec3(newpoly[1])

            for k = 1, polysize do
                newpoly[k+1] = makeVec3(newpoly[k+1])
                if ((newpoly[k].z <= pz) and (newpoly[k+1].z > pz)) or ((newpoly[k].z > pz) and (newpoly[k+1].z <= pz)) then
                    local vt = (pz - newpoly[k].z) / (newpoly[k+1].z - newpoly[k].z)
                    if (px < newpoly[k].x + vt*(newpoly[k+1].x - newpoly[k].x)) then
                        cn = cn + 1
                    end
                end
            end

            return cn%2 == 1
        else
            return false
        end
    end

--- DCS.Coord functions.
    --- Simple rounding function.
	-- From http://lua-users.org/wiki/SimpleRound
	---@param num number to round
	---@param idp number|nil use negative idp for rounding ahead of decimal place, positive for rounding after decimal place
    ---@return number
	function Utils.round(num, idp)
		local mult = 10^(idp or 0)
		return math.floor(num * mult + 0.5) / mult
	end

	--[[acc:
	in DM: decimal point of minutes.
	In DMS: decimal point of seconds.
	position after the decimal of the least significant digit:
	So:
	42.32 - acc of 2.
	]]
    ---@param lat number latitude.
    ---@param lon number longitude.
    ---@param acc number accuracy.
    ---@param DMS boolean if to use decimal point of seconds.
    ---@return string lat-lon coord formated.
	function Utils.tostringLL(lat, lon, acc, DMS)
        local round = Utils.round

		local latHemi, lonHemi
		if lat > 0 then
			latHemi = 'N'
		else
			latHemi = 'S'
		end

		if lon > 0 then
			lonHemi = 'E'
		else
			lonHemi = 'W'
		end

		lat = math.abs(lat)
		lon = math.abs(lon)

		local latDeg = math.floor(lat)
		local latMin = (lat - latDeg)*60

		local lonDeg = math.floor(lon)
		local lonMin = (lon - lonDeg)*60

		if DMS then	-- degrees, minutes, and seconds.
			local oldLatMin = latMin
			latMin = math.floor(latMin)
			local latSec = round((oldLatMin - latMin)*60, acc)

			local oldLonMin = lonMin
			lonMin = math.floor(lonMin)
			local lonSec = round((oldLonMin - lonMin)*60, acc)

			if latSec == 60 then
				latSec = 0
				latMin = latMin + 1
			end

			if lonSec == 60 then
				lonSec = 0
				lonMin = lonMin + 1
			end

			local secFrmtStr -- create the formatting string for the seconds place
			if acc <= 0 then	-- no decimal place.
				secFrmtStr = '%02d'
			else
				local width = 3 + acc	-- 01.310 - that's a width of 6, for example.
				secFrmtStr = '%0' .. width .. '.' .. acc .. 'f'
			end

			return string.format('%02d', latDeg) .. ' ' .. string.format('%02d', latMin) .. '\' ' .. string.format(secFrmtStr, latSec) .. '"' .. latHemi .. '	 '
			.. string.format('%02d', lonDeg) .. ' ' .. string.format('%02d', lonMin) .. '\' ' .. string.format(secFrmtStr, lonSec) .. '"' .. lonHemi

		else	-- degrees, decimal minutes.
			latMin = round(latMin, acc)
			lonMin = round(lonMin, acc)

			if latMin == 60 then
				latMin = 0
				latDeg = latDeg + 1
			end

			if lonMin == 60 then
				lonMin = 0
				lonDeg = lonDeg + 1
			end

			local minFrmtStr -- create the formatting string for the minutes place
			if acc <= 0 then	-- no decimal place.
				minFrmtStr = '%02d'
			else
				local width = 3 + acc	-- 01.310 - that's a width of 6, for example.
				minFrmtStr = '%0' .. width .. '.' .. acc .. 'f'
			end

			return string.format('%02d', latDeg) .. ' ' .. string.format(minFrmtStr, latMin) .. '\'' .. latHemi .. '	 '
			.. string.format('%02d', lonDeg) .. ' ' .. string.format(minFrmtStr, lonMin) .. '\'' .. lonHemi

		end
	end

--- Draw functions
    ---Get Markup ID.
    ---@return number markup ID.
    function Utils.getMarkupID()
        for i=1,#Utils.MapMarkupIDs+1,1 do
            if not Utils.MapMarkupIDs[i] then
                Utils.MapMarkupIDs[i] = true
                return i
            end
        end
    end

    ---Draw polygon zone with more than 3 vecticels.
    ---@param Coalition number coalition.side, 0 for NEUTRAL 1 for RED, 2 for BLUE.
    ---@param MarkID number
    ---@param vecs vec2[]|vec3[] {verticies[1]:vec2|vec3, verticies[2]:vec2|vec3, verticies[3]:vec2|vec3, verticies[4]:vec2|vec3, ...}
    ---@param Color table {r:number, g:number, b:number, a:number}
    ---@param FillColor table {r:number, g:number, b:number, a:number}
    ---@param LineType number 0 No Line, 1 Solid, 2 Dashed, 3 Dotted, 4 Dot Dash, 5 Long Dash, 6 Two Dash
    ---@param ReadOnly boolean if this markup read only.
    ---@param Text string|nil The message to display when the circle is added.
    function Utils.drawPolygon(Coalition,MarkID,vecs,Color,FillColor,LineType,ReadOnly,Text)
        local doString = Utils.doString

        local s=string.format("trigger.action.markupToAll(7, %d, %d,", Coalition, MarkID)
        for _,vec in ipairs(vecs) do
            vec = Utils.makeVec3(vec)
            --s=s..string.format("%s,", UTILS._OneLineSerialize(vec))
            s=s..string.format("{x=%.1f, y=%.1f, z=%.1f},", vec.x, vec.y, vec.z)
        end
        s=s..string.format("{%.3f, %.3f, %.3f, %.3f},", Color[1], Color[2], Color[3], Color[4])
        s=s..string.format("{%.3f, %.3f, %.3f, %.3f},", FillColor[1], FillColor[2], FillColor[3], FillColor[4])
        s=s..string.format("%d,", LineType or 1)
        s=s..string.format("%s", tostring(ReadOnly))
        if Text and type(Text)=="string" and string.len(Text)>0 then
            s=s..string.format(", \"%s\"", tostring(Text))
        end
        s=s..")"

        -- Execute string command
        local success=doString(s)

        if not success then
            env.error("ERROR: Could not draw polygon")
            env.info(s)
        end
    end

--- DCS 脚本体操
    --- Get object.
    ---@param object Object a DCS.Object
    ---@return Object|nil
    function Utils.getObject(object)
        if not object.isExist or not object:isExist() then return end

        return object
    end

    --- Get static object.
    ---@param DCSstaticObject string|StaticObject a DCS.StaticObject or its name.
    ---@param alive boolean if true will check if this staticObject is alive.
    ---@param isCargo boolean if true will check if this staticObject is cargo.
    ---@return StaticObject?
    function Utils.getStaticObject(DCSstaticObject,alive,isCargo)
        local getObject = Utils.getObject

        local staticObject = DCSstaticObject
        if type(staticObject)=='string' then
            staticObject = StaticObject.getByName(staticObject)
        end

        if not getObject(staticObject) then return end

        if alive then
            if not staticObject.getLife or staticObject:getLife() < 1 then return end
        end

        if isCargo then
            if not staticObject.getCargoDisplayName or not staticObject:getCargoDisplayName() then return end
            if not staticObject.getCargoWeight or not staticObject:getCargoWeight() then return end
        end

        return staticObject
    end

    --- Get weapon.
    ---@param weapon Weapon a DCS.Weapon.
    ---@return Weapon|nil
    function Utils.getWeapon(weapon)
        local getObject = Utils.getObject

        if not getObject(weapon) then return end
        if not weapon.getLauncher then return end
        if not weapon.getTarget then return end

        return weapon
    end

    --- Get Group.
    ---@param groupIdent string|Group Can be group name or DCS.Group.
    ---@return Group?
    function Utils.getGroup(groupIdent)
        local group = groupIdent
        if type(groupIdent)=='string' then
            group = Group.getByName(groupIdent)
        end

        if not group or not group.isExist or not group:isExist() then return end
        if not group.getUnit or not group.getUnits then return end
        if not group.getController then return end
        if not group.getID then return end
        if not group.getName then return end
        if not group.getCoalition then return end

        return group
    end

    --- Get Unit.
    ---@param DCSunit Unit|string a DCS.Unit object or its name.
    ---@param alive boolean if true will check if this unit is alive.
    ---@param player boolean if true will check if this unit controlled by player.
    ---@return Unit?
    function Utils.getUnit(DCSunit,alive,player)
        local unit = DCSunit
        if type(DCSunit)=='string' then
            unit = Unit.getByName(DCSunit)
        end

        if not unit then return end
        if not unit.getGroup then return end
        if not unit.getTypeName then return end
        if not unit.getID then return end
        if not unit.getCoalition then return end
        if not unit.getController then return end

        if not unit.isExist or not unit:isExist() then return end

        if alive then
            if not unit.getLife or unit:getLife()<1 then return end
        end

        if player then
            if not unit.getPlayerName or not unit:getPlayerName() then return end
        end

        return unit
    end

    --- Get unit(1).
    ---@param DCSgroup Group a DCS.Group object.
    ---@param alive boolean if true will check if this unit is alive.
    ---@param player boolean if true will check if this unit controlled by player.
    ---@return Unit|nil
    function Utils.getFirstUnit(DCSgroup,alive,player)
        local getGroup = Utils.getGroup
        local getUnit = Utils.getUnit

        local group = getGroup(DCSgroup)
        if not group then return end

        local unit = getUnit(group:getUnit(1),alive,player)
        if not unit then return end

        return unit
    end

    --- Get Unit's group.
    ---@param DCSunit Unit|string a DCS.Unit object or its name.
    ---@return Group|nil
    function Utils.getUnitGroup(DCSunit,alive,player)
        local getGroup = Utils.getGroup
        local getUnit = Utils.getUnit

        local unit = getUnit(DCSunit,alive,player)
        if not unit then return end

        return getGroup(unit:getGroup())
    end

--- DCS.Object functions.
    ---Get a object heading.
    ---@param object Object DCS.Object.
    ---@return number|boolean in radians.
	function Utils.getObjHeading(object)
        if not object then return false end

        local objectPos = object:getPosition()
        local objectHeading = math.atan2(objectPos.x.z, objectPos.x.x)

        return objectHeading
    end

    --- Returns an array containing a group's units positions.
	--	e.g.
	--		{
	--			[1] = {x = 299435.224, y = -1146632.6773},
	--			[2] = {x = 663324.6563, y = 322424.1112}
	--		}
	---@param groupIdent number|string group id or name
	---@return vec2[]? array containing positions of each group member
	function Utils.getWayPoints(groupIdent)
		-- search by groupId and allow groupId and groupName as inputs
		local gpId = groupIdent
		if type(groupIdent) == 'string' and not tonumber(groupIdent) then
			local group = Group.getByName(groupIdent)
            if not group then return end

            gpId = group:getID()
		end

		for coa_name, coa_data in pairs(env.mission.coalition) do
			if  type(coa_data) == 'table' then
				if coa_data.country then --there is a country table
					for cntry_id, cntry_data in pairs(coa_data.country) do
						for obj_cat_name, obj_cat_data in pairs(cntry_data) do
							if obj_cat_name == "helicopter" or obj_cat_name == "ship" or obj_cat_name == "plane" or obj_cat_name == "vehicle" then	-- only these types have points
								if ((type(obj_cat_data) == 'table') and obj_cat_data.group and (type(obj_cat_data.group) == 'table') and (#obj_cat_data.group > 0)) then	--there's a group!
									for group_num, group_data in pairs(obj_cat_data.group) do
										if group_data and group_data.groupId == gpId then -- this is the group we are looking for
											if group_data.route and group_data.route.points and #group_data.route.points > 0 then
												local points = {}
												for point_num, point in pairs(group_data.route.points) do
													if not point.point then
														points[point_num] = { x = point.x, y = point.y }
													else
														points[point_num] = point.point	--it's possible that the ME could move to the point = vec2 notation.
													end
												end
												return points
											end
											return
										end	--if group_data and group_data.name and group_data.name == 'groupname'
									end --for group_num, group_data in pairs(obj_cat_data.group) do
								end --if ((type(obj_cat_data) == 'table') and obj_cat_data.group and (type(obj_cat_data.group) == 'table') and (#obj_cat_data.group > 0)) then
							end --if obj_cat_name == "helicopter" or obj_cat_name == "ship" or obj_cat_name == "plane" or obj_cat_name == "vehicle" or obj_cat_name == "static" then
						end --for obj_cat_name, obj_cat_data in pairs(cntry_data) do
					end --for cntry_id, cntry_data in pairs(coa_data.country) do
				end --if coa_data.country then --there is a country table
			end --if coa_name == 'red' or coa_name == 'blue' and type(coa_data) == 'table' then
		end --for coa_name, coa_data in pairs(mission.coalition) do
	end




end

return Utils